package com.gz.moreaseManager.utils;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.Base64;

public class SecureUtil {

	/**
	 * 加密算法名
	 */
	private static final String MD5 = "MD5", HmacMD5 = "HmacMD5", SHA1 = "SHA1", HmacSHA1 = "HmacSHA1", DES = "DES", AES = "AES";

	/**
	 * 编码格式；默认使用uft-8
	 */
	private String charset = "utf-8";
	/**
	 * DES key size
	 */
	private int keysizeDES = 0;
	/**
	 * AES key size
	 */
	private int keysizeAES = 128;
	/**
	 * 实例对象
	 */
	private static SecureUtil secureUtil;

	private SecureUtil() {
		/**
		 * 构造函数，禁止实例化
		 */
	}

	public static SecureUtil builder() {
		if (secureUtil == null) {
			synchronized (SecureUtil.class) {
				if (secureUtil == null)
					secureUtil = new SecureUtil();
			}
		}
		return secureUtil;
	}

	/**
	 * MessageDigest单向加密（无密码）
	 *
	 * @param res       加密原文
	 * @param algorithm 算法名称
	 * @return 加密字符集
	 */
	private byte[] messageDigest(String res, String algorithm) {
		try {
			MessageDigest md = MessageDigest.getInstance(algorithm);
			return md.digest(charset == null ? res.getBytes() : res.getBytes(charset));
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * KeyGenerator单/双向加密（可设密码）
	 *
	 * @param res       加密原文
	 * @param algorithm 算法名称
	 * @param key       加密秘钥
	 * @return 加密字符集
	 */
	private byte[] keyGeneratorMac(String res, String algorithm, String key) {
		try {
			SecretKey secretKey;
			if (key == null) {
				secretKey = KeyGenerator.getInstance(algorithm).generateKey();
			} else {
				byte[] keyBytes = charset == null ? key.getBytes() : key.getBytes(charset);
				secretKey = new SecretKeySpec(keyBytes, algorithm);
			}
			Mac mac = Mac.getInstance(algorithm);
			mac.init(secretKey);
			return mac.doFinal(res.getBytes());
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * KeyGenerator(DES/AES)双向加密（注意!这里二进制转为十六进制字符串）
	 *
	 * @param res       加密原文
	 * @param algorithm 算法名称
	 * @param key       加密秘钥
	 * @param keySize   密钥长度
	 * @param encode    是否加密
	 * @return 加密密文
	 */
	private String keyGeneratorES(String res, String algorithm, String key, int keySize, boolean encode) {
		try {
			KeyGenerator kg = KeyGenerator.getInstance(algorithm);
			if (keySize == 0) {
				byte[] keyBytes = charset == null ? key.getBytes() : key.getBytes(charset);
				kg.init(new SecureRandom(keyBytes));
			} else if (key == null) {
				kg.init(keySize);
			} else {
				byte[] keyBytes = charset == null ? key.getBytes() : key.getBytes(charset);
				kg.init(keySize, new SecureRandom(keyBytes));
			}
			SecretKey secretKey = kg.generateKey();
			SecretKeySpec sks = new SecretKeySpec(secretKey.getEncoded(), algorithm);
			Cipher cipher = Cipher.getInstance(algorithm);
			if (encode) {
				cipher.init(Cipher.ENCRYPT_MODE, sks);
				byte[] bytes = charset == null ? res.getBytes() : res.getBytes(charset);
				return bytesToHexStr(cipher.doFinal(bytes));
			} else {
				cipher.init(Cipher.DECRYPT_MODE, sks);
				byte[] bytes = hexStrToBytes(res);
				if (bytes != null)
					return new String(cipher.doFinal(bytes));
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * 二进制转换成十六进制
	 *
	 * @param bytes 二进制字符集
	 * @return 十六进制字符串
	 */
	public String bytesToHexStr(byte[] bytes) {
		StringBuilder result = new StringBuilder();
		for (byte i : bytes) {
			String hex = Integer.toHexString(i & 0xFF);
			if (hex.length() == 1)
				hex = '0' + hex;
			result.append(hex);
		}
		return result.toString();
	}

	/**
	 * 将十六进制转换为二进制
	 *
	 * @param hexStr 十六进制字符串
	 * @return 二进制字符集
	 */
	public byte[] hexStrToBytes(String hexStr) {
		if (hexStr.length() < 1) return null;
		byte[] result = new byte[hexStr.length() / 2];
		for (int i = 0; i < hexStr.length() / 2; i++) {
			int high = Integer.parseInt(hexStr.substring(i * 2, i * 2 + 1), 16);
			int low = Integer.parseInt(hexStr.substring(i * 2 + 1, i * 2 + 2), 16);
			result[i] = (byte) (high * 16 + low);
		}
		return result;
	}

	/**
	 * MD5加密算法-加密（不可逆）
	 *
	 * @param res 加密原文
	 * @return 加密密文
	 */
	public String MD5encode(String res) {
		return bytesToHexStr(messageDigest(res, MD5));
	}

	/**
	 * MD5加密算法-加密（不可逆）
	 *
	 * @param res      加密原文
	 * @param toBase64 密文Base64加密
	 * @return 加密密文
	 */
	public String MD5encode(String res, Boolean toBase64) {
		byte[] bytes = messageDigest(res, MD5);
		if (toBase64 == null || !toBase64) return bytesToHexStr(bytes);
		return Base64encode(bytes);
	}

	/**
	 * MD5加密算法-加密（不可逆）
	 *
	 * @param res      加密原文
	 * @param key      加密秘钥
	 * @param toBase64 密文Base64加密
	 * @return 加密密文
	 */
	public String MD5encode(String res, String key, Boolean toBase64) {
		byte[] bytes = keyGeneratorMac(res, HmacMD5, key);
		if (toBase64 == null || !toBase64) return bytesToHexStr(bytes);
		return Base64encode(bytes);
	}

	/**
	 * SHA1加密算法-加密（不可逆）
	 *
	 * @param res 加密原文
	 * @return 加密密文
	 */
	public String SHA1encode(String res) {
		return bytesToHexStr(messageDigest(res, SHA1));
	}

	/**
	 * SHA1加密算法-加密（不可逆）
	 *
	 * @param res      加密原文
	 * @param toBase64 密文Base64加密
	 * @return 加密密文
	 */
	public String SHA1encode(String res, Boolean toBase64) {
		byte[] bytes = messageDigest(res, SHA1);
		if (toBase64 == null || !toBase64) return bytesToHexStr(bytes);
		return Base64encode(bytes);
	}

	/**
	 * SHA1加密算法-加密（不可逆）
	 *
	 * @param res      加密原文
	 * @param key      加密秘钥
	 * @param toBase64 密文Base64加密
	 * @return 加密密文
	 */
	public String SHA1encode(String res, String key, Boolean toBase64) {
		byte[] bytes = keyGeneratorMac(res, HmacSHA1, key);
		if (toBase64 == null || !toBase64) return bytesToHexStr(bytes);
		return Base64encode(bytes);
	}

	/**
	 * DES加密算法-加密（可逆）
	 *
	 * @param res 加密原文
	 * @param key 加密秘钥
	 * @return 加密密文
	 */
	public String DESencode(String res, String key) {
		return keyGeneratorES(res, DES, key, keysizeDES, true);
	}

	/**
	 * DES加密算法-解密
	 *
	 * @param res 加密密文
	 * @param key 加密秘钥
	 * @return 加密原文
	 */
	public String DESdecode(String res, String key) {
		return keyGeneratorES(res, DES, key, keysizeDES, false);
	}

	/**
	 * AES加密算法-加密（可逆）
	 *
	 * @param res 加密原文
	 * @param key 加密秘钥
	 * @return 加密密文
	 */
	public String AESencode(String res, String key) {
		return keyGeneratorES(res, AES, key, keysizeAES, true);
	}

	/**
	 * AES加密算法-解密
	 *
	 * @param res 加密密文
	 * @param key 加密秘钥
	 * @return 加密原文
	 */
	public String AESdecode(String res, String key) {
		return keyGeneratorES(res, AES, key, keysizeAES, false);
	}

	/**
	 * 异或加密算法-加密（可逆）
	 *
	 * @param res 加密原文
	 * @param key 加密秘钥
	 * @return 加密密文
	 */
	public String XORencode(String res, String key) {
		byte[] bytes = res.getBytes();
		for (int i = 0; i < bytes.length; i++) {
			bytes[i] = (byte) ((bytes[i]) ^ key.hashCode());
		}
		return bytesToHexStr(bytes);
	}

	/**
	 * 异或加密算法-解密
	 *
	 * @param res 加密密文
	 * @param key 加密秘钥
	 * @return 加密原文
	 */
	public String XORdecode(String res, String key) {
		byte[] bytes = hexStrToBytes(res);
		if (bytes == null) return null;
		for (int i = 0; i < bytes.length; i++) {
			bytes[i] = (byte) ((bytes[i]) ^ key.hashCode());
		}
		return new String(bytes);
	}


	/**
	 * Base64加密
	 *
	 * @param res 加密原文
	 * @return 加密密文
	 */
	public String Base64encode(byte[] res) {
		return new String(Base64.getEncoder().encode(res));
	}

	/**
	 * Base64加密算法-加密（可逆）
	 *
	 * @param res 加密原文
	 * @return 加密密文
	 */
	public String Base64encode(String res) {
		return new String(Base64.getEncoder().encode(res.getBytes()));
	}

	/**
	 * Base64加密算法-解密
	 *
	 * @param res 加密密文
	 * @return 加密密文
	 */
	public String Base64decode(String res) {
		return new String(Base64.getDecoder().decode(res));
	}

	/**
	 * 自定义解密
	 *
	 * @param res  加密字符串
	 * @param salt 秘钥/盐值
	 * @return 加密原文
	 */
	public String mineEncode(String res, String salt) {
		String deStr = Base64decode(res);
		String[] arrays = deStr.split("");
		boolean fmix = salt.length() % 2 == 0;
		int charLen = arrays.length, saltLen = charLen - 32;
		int mol = fmix ? 1 : 0, forLen = saltLen * 2;
		forLen = forLen > charLen ? charLen : forLen;
		StringBuilder deCode = new StringBuilder();
		for (int i = 0; i < forLen; i++) {
			if (i % 2 != mol)
				deCode.append(arrays[i]);
			if (i == forLen - 1)
				deCode.append(deStr.substring(i + 1));
		}
		return deCode.toString();
	}
}